package cheme.Interpreter.AST.SystemSolver;

import java.util.Enumeration;

import visitor.*;

import cheme.Interpreter.AST.Arithmetic.AstAddition;
import cheme.Interpreter.AST.Arithmetic.AstDivision;
import cheme.Interpreter.AST.Arithmetic.AstExponential;
import cheme.Interpreter.AST.Arithmetic.AstExpression;
import cheme.Interpreter.AST.Arithmetic.AstMultiplication;
import cheme.Interpreter.AST.Arithmetic.AstNegation;
import cheme.Interpreter.AST.Arithmetic.AstSubtraction;
import cheme.Interpreter.AST.Arithmetic.AstValue;
import cheme.Interpreter.AST.Interfaces.IAstBinaryOperation;
import cheme.Interpreter.AST.Interfaces.IAstExpression;
import cheme.Interpreter.AST.Interfaces.IAstNode;
import cheme.Interpreter.AST.Interfaces.IAstString;
import cheme.Interpreter.AST.Interfaces.IAstType;
import cheme.Interpreter.AST.Interfaces.IAstValue;
import cheme.Interpreter.AST.Interfaces.IAstVariableReference;
import cheme.Interpreter.AST.SymbolTable.SymbolTable;
import cheme.Interpreter.AST.Type.AstType;
import cheme.Interpreter.AST.Type.AstUnit;
import cheme.Interpreter.AST.Variable.AstVariableReference;

import syntaxtree.*;

public class ChemicalVisitor extends GJDepthFirst<IAstNode, IAstNode>
{

	SymbolTable symbolTable = null;

	public SymbolTable getSymbolTable()
	{
		return symbolTable;
	}
	
	public ChemicalVisitor(SymbolTable symbolTable)
	{
		this.symbolTable = symbolTable;
	}
	
	public static void Print(Object obj)
	{
		//System.out.print(obj);
	}
	
	//
	// Auto class visitors--probably don't need to be overridden.
	//
	public IAstNode visit(NodeList n, IAstNode argu)
	{
		IAstNode _ret = null;
		int _count = 0;
		for(Enumeration<Node> e = n.elements(); e.hasMoreElements();)
		{
			e.nextElement().accept(this, argu);
			_count++;
		}
		return _ret;
	}

	public IAstNode visit(NodeListOptional n, IAstNode argu)
	{
		if(n.present())
		{
			IAstNode _ret = null;
			int _count = 0;
			for(Enumeration<Node> e = n.elements(); e.hasMoreElements();)
			{
				e.nextElement().accept(this, argu);
				_count++;
			}
			return _ret;
		}
		else
			return null;
	}

	public IAstNode visit(NodeOptional n, IAstNode argu)
	{
		if(n.present())
			return n.node.accept(this, argu);
		else
			return null;
	}

	public IAstNode visit(NodeSequence n, IAstNode argu)
	{
		IAstNode _ret = null;
		int _count = 0;
		for(Enumeration<Node> e = n.elements(); e.hasMoreElements();)
		{
			e.nextElement().accept(this, argu);
			_count++;
		}
		return _ret;
	}

	public IAstNode visit(NodeToken n, IAstNode argu)
	{
		return null;
	}
	
	//
	// User-generated visitor methods below
	//
	
	/**
	 * f0 -> Term() f1 -> ( TermTail() )?
	 */
	public IAstExpression visit(Expression n, IAstNode argu)
	{
		IAstExpression expression = (IAstExpression)n.f0.accept(this, argu);
		if(n.f1.present())
		{
			IAstBinaryOperation binOp = (IAstBinaryOperation)n.f1.accept(this, argu);
			
			//the first UnaryExpressionbecomes the lhs
			binOp.setLhs(expression);
			expression = binOp;
		}

		return expression;
	}

	/**
	 * f0 -> ( TermTailElement() )+
	 */
	public IAstBinaryOperation visit(TermTail n, IAstNode argu)
	{
		/*
		 * The UnaryTailUnit is a (MultiplierChoice | DivisorTerm | ExponentialFunction)
		 * So it will give a freshly minted IAstBinaryOperation. All this function needs
		 * to do is manage the composition of the returned functions.
		 * 
		 * Also, the first child is sure to be present to process that, then 
		 * loop over potential others.
		 */
		IAstBinaryOperation currentBinOp = null;
		IAstBinaryOperation newBinOp = null; 
		Enumeration<Node> e = n.f0.elements();
		if(e.hasMoreElements())
		{
			currentBinOp = (IAstBinaryOperation)e.nextElement().accept(this, argu);

			while(e.hasMoreElements())
			{
				newBinOp = (IAstBinaryOperation)e.nextElement().accept(this, argu);
				newBinOp.setLhs(currentBinOp);
				currentBinOp = newBinOp;
			}
		}
		
		return currentBinOp;
	}

	/**
	 * f0 -> ( AddendTerm() | SubtrahendTerm() )
	 */
	public IAstBinaryOperation visit(TermTailElement n, IAstNode argu)
	{
		return (IAstBinaryOperation)n.f0.accept(this, argu);
	}

	/**
	 * f0 -> "+" f1 -> Term()
	 */
	public IAstBinaryOperation visit(AddendTerm n, IAstNode argu)
	{

		return new AstAddition(null, (IAstExpression)n.f1.accept(this, argu));
	}

	/**
	 * f0 -> "-" f1 -> Term()
	 */
	public IAstBinaryOperation visit(SubtrahendTerm n, IAstNode argu)
	{
		return new AstSubtraction(null, (IAstExpression)n.f1.accept(this, argu));
	}

	/**
	 * f0 -> UnaryExpression() f1 -> ( UnaryTail() )?
	 */
	public IAstExpression visit(Term n, IAstNode argu)
	{
		IAstExpression expression = (IAstExpression)n.f0.accept(this, argu);
		
		/*
		 * If there is a UnaryTail and so this is a binary expression...
		 */
		if(n.f1.present())
		{
			IAstBinaryOperation binOp = (IAstBinaryOperation)n.f1.accept(this, argu);
			
			//the first UnaryExpressionbecomes the lhs
			binOp.setLhs(expression);
			expression = binOp;
		}
		
		return expression;
	}

	/**
	 * f0 -> ( UnaryTailUnit() )+
	 */
	public IAstBinaryOperation visit(UnaryTail n, IAstNode argu)
	{
		/*
		 * The UnaryTailUnit is a (MultiplierChoice | DivisorTerm | ExponentialFunction)
		 * So it will give a freshly minted IAstBinaryOperation. All this function needs
		 * to do is manage the composition of the returned functions.
		 * 
		 * Also, the first child is sure to be present to process that, then 
		 * loop over potential others.
		 */
		IAstBinaryOperation currentBinOp = null;
		IAstBinaryOperation newBinOp = null; 
		Enumeration<Node> e = n.f0.elements();
		if(e.hasMoreElements())
		{
			currentBinOp = (IAstBinaryOperation)e.nextElement().accept(this, argu);

			while(e.hasMoreElements())
			{
				newBinOp = (IAstBinaryOperation)e.nextElement().accept(this, argu);
				newBinOp.setLhs(currentBinOp);
				currentBinOp = newBinOp;
			}
		}
		
		return currentBinOp;
	}

	/**
	 * f0 -> ( MultiplierChoice() | DivisorTerm() | ExponentialFunction() )
	 */
	public IAstBinaryOperation visit(UnaryTailUnit n, IAstNode argu)
	{
		return (IAstBinaryOperation)n.f0.accept(this, argu);
	}

	/**
	 * f0 -> "/" f1 -> Term()
	 */
	public IAstBinaryOperation visit(DivisorTerm n, IAstNode argu)
	{
		return new AstDivision(null, (IAstExpression)n.f1.accept(this, argu));
	}

	/**
	 * f0 -> ( MultiplierTerm() | ComplexFactor() )
	 */
	public IAstBinaryOperation visit(MultiplierChoice n, IAstNode argu)
	{
		return new AstMultiplication(null, (IAstExpression)n.f0.choice.accept(this, argu));
	}

	/**
	 * f0 -> "*" f1 -> Term()
	 */
	public IAstExpression visit(MultiplierTerm n, IAstNode argu)
	{
		return (IAstExpression)n.f1.accept(this, argu);
	}

	/**
	 * f0 -> "^" f1 -> Expression()
	 */
	public IAstBinaryOperation visit(ExponentialFunction n, IAstNode argu)
	{
		return new AstExponential(null, (IAstExpression)n.f1.accept(this, argu));
	}

	/**
	 * f0 -> Negation() | Factor()
	 */
	public IAstExpression visit(UnaryExpression n, IAstNode argu)
	{
		return (IAstExpression)n.f0.accept(this, argu);
	}

	/**
	 * f0 -> "-" f1 -> Factor()
	 */
	public IAstExpression visit(Negation n, IAstNode argu)
	{
		return new AstNegation((IAstExpression)n.f0.accept(this, argu));
	}

	/**
	 * f0 -> ( FactorChoice() )? f1 -> ( TypeDecoration() )?
	 */
	public IAstExpression visit(Factor n, IAstNode argu)
	{
		IAstExpression expression = null;
		IAstType type = null;
		
//		if(n.f0.present())
//		{
//			expression = (IAstExpression)n.f0.accept(this, argu);
//		}
		
		expression = (IAstExpression)n.f0.accept(this, argu);

		if(n.f1.present())
		{
			type = (IAstType)n.f1.accept(this, argu);
		}

		return new AstExpression(expression, type);
	}

	/**
	 * f0 -> ( LiteralFactor() | ComplexFactor() )
	 * 
	 * Both choices resolve to an expression so we an return that and ignore 
	 * specific types.
	 */
	public IAstExpression visit(FactorChoice n, IAstNode argu)
	{
		return (IAstExpression)n.f0.accept(this, argu);
	}

	/**
	 * f0 -> UnknownFactor() | IntegerFactor() | FloatFactor() |
	 * Identifier()
	 */
	public IAstNode visit(LiteralFactor n, IAstNode argu)
	{
		return n.f0.accept(this, argu);
	}
	
	/**
	 * f0 -> "?"
	 */
	public IAstValue visit(UnknownFactor n, IAstNode argu)
	{
		/*
		 * Return a 'unknown value' this will indicate the existence of a 
		 * varaible in the expression.
		 */
		return new AstValue();
	}

	/**
	 * f0 -> <INTEGER>
	 */
	public IAstValue visit(IntegerFactor n, IAstNode argu)
	{
		return new AstValue(Double.parseDouble(n.f0.tokenImage));
	}

	/**
	 * f0 -> <FLOAT>
	 */
	public IAstValue visit(FloatFactor n, IAstNode argu)
	{
		return new AstValue(Double.parseDouble(n.f0.tokenImage));
	}

	/**
	 * f0 -> "(" f1 -> Expression() f2 -> ")"
	 */
	public IAstExpression visit(ComplexFactor n, IAstNode argu)
	{
		return (IAstExpression)n.f1.accept(this, argu);
	}

	/**
	 * f0 -> <IDENTIFIER>
	 */
	public IAstVariableReference visit(Identifier n, IAstNode argu)
	{
		return new AstVariableReference(getSymbolTable(), n.f0.tokenImage);
	}

	/**
	 * f0 -> Unit() f1 -> ( Material() )?
	 */
	public IAstType visit(TypeDecoration n, IAstNode argu)
	{
		IAstString unit = (IAstString)n.f0.accept(this, argu);

		IAstString material = null;
		if(n.f1.present())
		{
			material = (IAstString)n.f1.accept(this, argu);
		}

		return new AstType(unit, material);
	}

	/**
	 * f0 -> <STANDARD_UNIT>
	 */
	public IAstString visit(Unit n, IAstNode argu)
	{
		return new AstUnit(n.f0.tokenImage);
	}

//	/**
//	 * f0 -> <CHEMICAL> | <IDENTIFIER>
//	 */
//	public IAstString visit(Material n, IAstNode argu)
//	{
//		return new AstMaterial(n.f0.choice.toString());
//	}
}
